package model;

import java.awt.Scrollbar;
import java.awt.event.MouseWheelEvent;
import java.util.Vector;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Observable;

/**
 * 06-01-10
 * 
 * @author Dave Nieuwenhuijzen & Timo Janssen
 */
public class CalendarModel extends Observable {
	private CalendarViewMode calendarViewMode;
	private int scrollValue = 0;
	private int scrollMax = 100;
	private Scrollbar scrollbar = null;

	private int[] limitsHorizontal = new int[8];
	private int[] limitsVertical = new int[7];

	private Calendar selectedDate = null;
	private Vector<model.AppointmentDataModel> appointmentData;

	public CalendarModel() {
		selectedDate = new GregorianCalendar();
		appointmentData = new Vector<model.AppointmentDataModel>();
	}

	/**
	 * Get the current calendar view.
	 * 
	 * @return CalendarViewMode;
	 */
	public CalendarViewMode getCalendarViewMode() {
		return calendarViewMode;
	}

	/**
	 * Set the current calendar view.
	 * 
	 * @param setCalendarViewMode The new view
	 */
	public void setCalendarViewMode(CalendarViewMode setCalendarViewMode) {
		calendarViewMode = setCalendarViewMode;

		setChanged();
		notifyObservers();
	}

	/**
	 * Get the last days of the previous month, this is used in the month view to
	 * fill up the gap before the first day of the current month.
	 * 
	 * @param currentMonth The current month
	 * @param currentYear The current year
	 * @return int[] An array with the last days
	 */
	public int[] getLastDaysOfPreviousMonth(int currentYear, int currentMonth) {	
		if((currentMonth - 1) < 0) {
			currentYear -= 1;
			currentMonth = 12;
		}

		Calendar calendar = new GregorianCalendar(currentYear, currentMonth, 1);
		int currentDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;

		Calendar lastMonth = new GregorianCalendar(currentYear, currentMonth - 1, 1);
		int daysInLastMonth = lastMonth.getActualMaximum(Calendar.DAY_OF_MONTH);

		int[] dates = new int[7];
		for(int i = currentDayOfWeek; i >= 0; i--) {
			dates[i] = daysInLastMonth + i - currentDayOfWeek;
		}

		return dates;
	}

	/**
	 * Return all the appointments.
	 * 
	 * @return All the appointments
	 */
	public Vector<model.AppointmentDataModel> getAppointmentData() {
		return appointmentData;
	}

	/**
	 * Set all the appointments.
	 * 
	 * @param setAppointmentData The Vector with all the appointments to add
	 */
	public void setAppointmentData(Vector<model.AppointmentDataModel> setAppointmentData) {
		appointmentData = setAppointmentData;
	}

	/**
	 * Return the selected date
	 * 
	 * @return The selected date
	 */
	public Calendar getSelectedDate() {
		return selectedDate;
	}

	/**
	 * Set the selected date
	 * 
	 * @param setSelectedDate The selected date to set
	 */
	public void setSelectedDate(Calendar setSelectedDate) {
		selectedDate = setSelectedDate;
	}

	/**
	 * Return the width according to the index. In this array we store the widths for the week & month view.
	 * 
	 * @param index The index from array
	 * @return The width according to index
	 * @throws ArrayIndexOutOfBoundsException
	 */
	public int getLimitsHorizontal(int index) {
		try{
			return limitsHorizontal[index];
		} catch(ArrayIndexOutOfBoundsException e) {
			return limitsHorizontal[0];
		}
	}

	/**
	 * Add a width to the array at the index in the array
	 * 
	 * @param index The index where the value should be add
	 * @param value The value to add
	 */
	public void setLimitsHorizontal(int index, int value) {
		try{
			limitsHorizontal[index] = value;
		} catch(ArrayIndexOutOfBoundsException e) {
			System.err.println("Index is greater then array bounds");
		}
	}

	/**
	 * Return the height according to the index. In this array we store the heights for the week & month view.
	 * 
	 * @param index The index from array
	 * @return The height according to index
	 * @throws ArrayIndexOutOfBoundsException
	 */
	public int getLimitsVertical(int index) {
		try{
			return limitsVertical[index];
		} catch(ArrayIndexOutOfBoundsException e) {
			return limitsVertical[0];
		}
	}

	/**
	 * Add a width to the array at the index in the array
	 * 
	 * @param index The index where the value should be add
	 * @param value The value to add
	 */
	public void setLimitsVertical(int index, int value) {
		try{
			limitsVertical[index] = value;
		} catch(ArrayIndexOutOfBoundsException e) {
			System.err.println("Index is greater then array bounds");
		}
	}

	/**
	 * Get the scrollbar
	 * 
	 * @return The scrollbar
	 */
	public Scrollbar getScrollbar() {
		return scrollbar;
	}

	/**
	 * set the scrollbar
	 * 
	 * @param setScrollbar The scrollbar
	 */

	public void setScrollbar(Scrollbar setScrollbar) {
		scrollbar = setScrollbar;
	}
	
	/**
	 * Moves the scrollbar with normal clicking.
	 */
	public void setScroll() {
		setScroll(scrollbar);
	}
	
	/**
	 * Moves the scrollbar with normal clicking.
	 * 
	 * @param scrollbar The scrollbar
	 */
	public void setScroll(Scrollbar scrollbar) {
		int scrollValue = ((scrollbar.getValue() * scrollbar.getMaximum()) / (scrollbar.getMaximum() - scrollbar.getBlockIncrement() + 1));
		if (scrollValue > scrollbar.getMaximum()) {
			scrollValue = scrollbar.getMaximum();
		}

		setScrollValue(scrollValue);
	}
	
	/**
	 * The scrollbar is usable with the mousewheel with this method.
	 *
	 * @param e The event of the mouse event
	 */
	public void setScroll(MouseWheelEvent e){
		int notches = e.getWheelRotation();
		if (notches < 0) {
			if (scrollValue > scrollbar.getMinimum()){
				setScrollValue(getScrollValue() - e.getScrollAmount()-20);
			} else {
				setScrollValue(scrollbar.getMinimum());				
			}
		} else {
			if (scrollValue < scrollbar.getMaximum()){
				setScrollValue(getScrollValue() + e.getScrollAmount()+20);
			} else {
				setScrollValue(scrollbar.getMaximum());				
			}
		}
		
		getScrollbar().setValue(scrollValue);
	}

	/**
	 * Determine days of this week.
	 * 
	 * @return Calendar[]
	 */
	public Calendar[] calculateWeekDays() {
		return calculateWeekDays(new GregorianCalendar());
	}

	public Calendar[] calculateWeekDays(Calendar date) {
		Calendar[] dates = new GregorianCalendar[7];
		for (int i = 0; i < 7; i++) {
			dates[i] = new GregorianCalendar(date.get(Calendar.YEAR), date.get(Calendar.MONTH), date.get(Calendar.DAY_OF_MONTH));
			dates[i].add(Calendar.DAY_OF_YEAR, -date.get(Calendar.DAY_OF_WEEK)+i+1 );
		}

		// Every day has hour 0, minute 0 and second 0, this causes problems when we need to check the last day.
		// Example:
		// last day = 2010-01-23 00:00:00
		// Check date = 2010-01-23 08:30:00
		// If we want to check we get false back because 08:30:00 is greater then 00:00:00, for this reason we set the 
		// time of the last day to 23:59:59.
		if(dates.length == 7) {
			dates[6].add(Calendar.HOUR_OF_DAY, 23);
			dates[6].add(Calendar.MINUTE, 59);
			dates[6].add(Calendar.SECOND, 59);
		}

		return dates;
	}

	/**
	 * Calculate the width of the weekday columns.
	 * 
	 * @param width The width of the JPanel
	 */
	public void calculateWeekDayWidth(int width) {
		setLimitsHorizontal(0, 40);
		setLimitsHorizontal(7, width - 1);

		for (int i = 1; i < 7; i++) {
			setLimitsHorizontal(i, (((getLimitsHorizontal(7) - 40) * i) / 7) + 40);
		}
	}

	/**
	 * Return the scroll value
	 * 
	 * @return The scroll value
	 */
	public int getScrollValue() {
		return scrollValue;
	}

	/**
	 * Set the scroll value
	 * 
	 * @param setScrollValue
	 */
	public void setScrollValue(int setScrollValue) {
		scrollValue = setScrollValue;

		setChanged();
		notifyObservers();
	}

	/**
	 * Return the scroll max
	 * 
	 * @return The scroll max
	 */
	public int getScrollMax() {
		return scrollMax;
	}

	/**
	 * Set the scroll max
	 * 
	 * @param setScrollMax
	 */
	public void setScrollMax(int setScrollMax) {
		this.scrollMax = setScrollMax;
	}

	/**
	 * Return the hour and minute according to where you clicked in the week view
	 * 
	 * @param xPosition The x-position of the mouse when you clicked
	 * @param yPosition The y-position of the mouse when you clicked
	 * @return Calendar Calendar object with selected hour and minute
	 */
	public Calendar getPositionDateTimeWeekView(double xPosition, double yPosition) {
		Calendar[] weekDays = calculateWeekDays(selectedDate);

		if(selectedDate != null) {
			weekDays = calculateWeekDays(selectedDate);
		}

		Calendar calendar = weekDays[0];

		// Return null if any of the statements below are true:
		// - The x-position of mouse is less then first value of array
		// - The x-position of mouse is greater then last value of array
		// - The y-position of mouse is less then 0
		// - The y-position of mouse is greater or equal to scrollMax		
		if((xPosition < getLimitsHorizontal(0)) || (xPosition >= getLimitsHorizontal(7)) || (yPosition < 0) || (yPosition >= getScrollMax())) {
			return null;
		}

		int minutes = (15 * (int)Math.round((((yPosition * (24 * 60)) / getScrollMax())) / 15));

		calendar.add(Calendar.MINUTE, minutes);

		// We loop through all horizontal widths and if the current x position is in range we add 1 day to date
		for(int i = 0; i < 7; i++) {
			if((xPosition >= getLimitsHorizontal(i)) && xPosition <= getLimitsHorizontal(i + 1)) {
				break;
			}

			calendar.add(Calendar.DAY_OF_WEEK, 1);
		}

		return calendar;
	}

	/**
	 * Return the date according to the x and y position the mouse was when you clicked somewhere in the month view
	 * 
	 * @return Calendar object with the selected date.
	 */
	public Calendar getPositionDateTimeMonthView(double xPosition, double yPosition) {
		Calendar calendar = (Calendar) selectedDate.clone();
		calendar.set(getSelectedDate().get(Calendar.YEAR), getSelectedDate().get(Calendar.MONTH), 1);
		
		int[] prevMonthDays = getLastDaysOfPreviousMonth(getSelectedDate().get(Calendar.YEAR), getSelectedDate().get(Calendar.MONTH));
		
		// Get the number of days in previous month
		int amountPrevMonthDays = 0;
		for(int day : prevMonthDays) {
			if(day != 0) {
				amountPrevMonthDays++;
			}
		}
		
		amountPrevMonthDays--;
		
		if((xPosition < getLimitsHorizontal(0)) || (xPosition >= getLimitsHorizontal(7)) || (yPosition < 0) || (yPosition >= getScrollMax())) {
			return null; 
		}

		// We loop through all horizontal widths and if the current x position is in range we add 1 day to date
		for(int i = 0; i < 7; i++) {
			if((xPosition >= getLimitsHorizontal(i)) && (xPosition <= getLimitsHorizontal(i + 1))) {
				break;
			}

			calendar.add(Calendar.DAY_OF_MONTH, 1);
		}

		// We loop through all vertical heights and if the current y position is in range we add 7 days to date
		for(int i = 0; i < 7; i++) {
			if((yPosition >= getLimitsVertical(i)) && (yPosition <= getLimitsVertical(i + 1))) {
				break;
			}

			calendar.add(Calendar.DAY_OF_MONTH, 7);
		}

		// Subtract the amount of days in previous month. 
		calendar.add(Calendar.DAY_OF_WEEK, -amountPrevMonthDays);

		return calendar;
	}

	/**
	 * Get the position of the time.
	 * 
	 * @param hour The hour
	 * @param minute The minute
	 * @return The position of the time int int
	 */
	public int getPositionHours(double hour, double minute) {
		double position = hour + (minute / 60);
		// We have to multiply position by scrollMax in case we view the hours 0 - 11 and
		// the time is 13 for example
		return (int)((scrollMax * position) / 24);
	}

	/**
	 * Set selectedDate to next month or week, according to the current view
	 */
	public void next() {
		switch(calendarViewMode) {
		case week:
			selectedDate.add(Calendar.DAY_OF_MONTH, 7);	
			break;

		case month:
			selectedDate.add(Calendar.MONTH, 1);
			break;
		}

		setChanged();
		notifyObservers();
	}

	/**
	 * Set selectedDate to previous month or week, according to the current view
	 */
	public void previous() {
		switch(calendarViewMode) {
		case week:
			selectedDate.add(Calendar.DAY_OF_MONTH, -7);	
			break;

		case month:
			selectedDate.add(Calendar.MONTH, -1);
			break;
		}

		setChanged();
		notifyObservers();
	}
}